package com.yoso.file.sdk.utils;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.file.FileWriter;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ZipUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.fastjson2.JSON;
import com.google.common.collect.Lists;
import com.yoso.core.exception.BaseException;

import com.yoso.file.sdk.bo.ExportData;
import com.yoso.file.sdk.bo.Upload;
import com.yoso.file.sdk.enums.ExportType;
import com.yoso.file.sdk.enums.FileTypeEnum;
import com.yoso.file.sdk.enums.UploadType;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

@Slf4j
public class ExportUtil {
    /**
     * 读取文件数据
     *
     * @param file 文件
     * @return java.util.List<com.ncamc.next.market.common.vo.ApiExportVo>
     * @author shizejin
     * @date 2023/5/24 17:06
     */
    public static <V> List<V> readExcel(MultipartFile file, Class<V> cls){
        try {
            String fileName = excelFileThrowValid(file);
            List<V> dataList = readExcel(file.getInputStream(), cls);
            log.info("读取文件：{}---{}条数据", fileName,
                    Optional.ofNullable(dataList).map(Collection::size).orElse(0));
            return dataList;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 读取json文件数据
     *
     * @param file 文件
     * @return java.util.List<com.ncamc.next.market.common.vo.ApiExportVo>
     * @author shizejin
     * @date 2023/5/24 17:06
     */
    public static <V> List<V> readJson(MultipartFile file, Class<V> cls){
        try (InputStream inputStream = file.getInputStream()){
            String fileName = jsonFileThrowValid(file);
            log.info("读取文件：{}", fileName);
            return readJson(inputStream, cls);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 读取json文件数据
     *
     * @param file 文件
     * @return java.util.List<com.ncamc.next.market.common.vo.ApiExportVo>
     * @author shizejin
     * @date 2023/5/24 17:06
     */
    public static <V> List<V> readJson(File file, Class<V> cls){
        try (InputStream inputStream = FileUtil.getInputStream(file)){
            String fileName = jsonFileThrowValid(file.getName());
            log.info("读取文件：{}", fileName);
            return readJson(inputStream, cls);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 读取json文件数据
     *
     * @param inputStream 文件流
     * @param cls 对象
     * @return {@link List }<{@link V }>
     * @author shizejin
     * @date 2023/10/19 14:24
     */
    private static <V> List<V> readJson(InputStream inputStream, Class<V> cls){
        String json = IoUtil.read(inputStream, StandardCharsets.UTF_8);
        List<V> dataList = JSON.parseArray(json, cls);
        log.info("{}条数据", Optional.ofNullable(dataList).map(Collection::size).orElse(0));
        return dataList;
    }


    /**
     * 读取json文件数据
     *
     * @param inputStream 文件流
     * @return {@link ExportData }
     * @author shizejin
     * @date 2023/10/19 14:24
     */
    private static ExportData readWrapperJson(InputStream inputStream){
        String json = IoUtil.read(inputStream, StandardCharsets.UTF_8);
        return JSON.parseObject(json, ExportData.class);
    }

    /**
     * 导出Json
     *
     * @param response     响应
     * @param data         数据
     * @param fileName     文件名
     * @param businessCode 业务编码：导入导出进行匹配的标识
     * @param operator     操作人
     * @param sourceSpl    数据来源
     * @author shizejin
     * @date 2024/04/16 14:53
     */
    public static void exportWrapperJson(HttpServletResponse response, Object data, String fileName,
                                         String businessCode, String operator, Supplier<String> sourceSpl) {
        Assert.notBlank(businessCode, "业务编码不能为空");
        ExportData exportData = ExportData.init(data, businessCode, operator);
        if (sourceSpl != null) {
            exportData.setSource(sourceSpl.get());
        }
        exportJson(response, exportData, fileName);
    }

    /**
     * 读取文件数据
     *
     * @param inputStream 文件流
     * @param cls class
     * @return java.util.List<V>
     * @author shizejin
     * @date 2023/5/25 10:39
     */
    public static <V> List<V> readExcel(InputStream inputStream, Class<V> cls){
        return EasyExcel.read(inputStream).head(cls).sheet(0).doReadSync();
    }

    /**
     * 文件校验
     *
     * @param file 文件
     * @return java.lang.String 文件名
     * @author shizejin
     * @date 2023/5/25 10:42
     */
    private static String excelFileThrowValid(MultipartFile file) {
        String fileName = Optional.ofNullable(file)
                .map(MultipartFile::getOriginalFilename).orElse("");
        if (!StrUtil.containsAny(
                fileName, ExcelTypeEnum.XLSX.getValue(), ExcelTypeEnum.XLS.getValue())) {
            throw new BaseException("文件为空或格式不正确");
        }
        return fileName;
    }

    /**
     * 文件校验
     *
     * @param file 文件
     * @return java.lang.String 文件名
     * @author shizejin
     * @date 2023/5/25 10:42
     */
    private static String jsonFileThrowValid(MultipartFile file) {
        String fileName = Optional.ofNullable(file)
                .map(MultipartFile::getOriginalFilename).orElse("");
        return jsonFileThrowValid(fileName);
    }

    /**
     * 文件校验
     *
     * @param fileName 文件名
     * @return java.lang.String 文件名
     * @author shizejin
     * @date 2023/5/25 10:42
     */
    private static String jsonFileThrowValid(String fileName) {
        if (!StrUtil.contains(fileName, FileTypeEnum.JSON.getSuffix())) {
            throw new BaseException("文件为空或格式不正确");
        }
        return fileName;
    }

    /**
     * 导出文件流响应头
     *
     * @param response 响应
     * @param fileName 文件名
     * @param FileTypeEnum  文件格式
     * @author shizejin
     * @date 2023/5/25 11:14
     */
    public static void exportHttpResponseHeader(
            HttpServletResponse response, String fileName, FileTypeEnum FileTypeEnum) {

        exportHttpResponseHeader(response, "application/octet-stream", fileName, FileTypeEnum);
    }

    /**
     * 导出文件流响应头
     *
     * @param response 响应
     * @param fileName 文件名
     * @param FileTypeEnum  文件格式
     * @author shizejin
     * @date 2023/5/25 11:14
     */
    public static void exportHttpResponseHeader(
            HttpServletResponse response, String contentType, String fileName, FileTypeEnum FileTypeEnum) {
        //设置响应头 中文文件名需要使用URLEncoder.encode()编码
        try {
            fileName = parseFileName(fileName, FileTypeEnum);
            response.setHeader("Content-disposition",
                    "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
//            通用的二进制文件流
            response.setContentType(contentType);
            response.setCharacterEncoding("UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 解析文件名
     *
     * @param fileName 用户自定义的文件名
     * @param FileTypeEnum 文件格式
     * @return java.lang.String
     * @author shizejin
     * @date 2023/5/24 11:09
     */
    private static String parseFileName(String fileName, FileTypeEnum FileTypeEnum) {
        if (ObjectUtil.isNull(FileTypeEnum)) {
            throw new BaseException("文件格式为空");
        }
//        以用户自定义文件文件名为主，其次默认当前日期作为文件名
        String finalName = StrUtil.isBlank(fileName) ? DateUtil.today() : fileName;
//        用户在自定义文件名时可能带后缀，这里直接去掉
        int index = finalName.indexOf(".");
        finalName = index == -1 ? finalName : finalName.substring(0, index);
//        添加后缀
        return finalName.concat(FileTypeEnum.getSuffix());
    }



    /**
     * 导入数据
     *
     * @param cover 覆盖导入
     * @param difference   差异导入
     * @author shizejin
     * @date 2023/5/25 19:52
     */
    public static <U extends Upload> void upload(U upload,
                                                 Consumer<U> cover, Consumer<U> difference) {
        UploadType typeEnum = UploadType.getByCode(upload.getUploadType());
        uploadTypeThrowValid(typeEnum);
        List dataList = upload.getDataList();
        upload.setDataList((List) dataList.stream().filter(ObjectUtil::isNotNull).collect(Collectors.toList()));
        switch (typeEnum) {
//            覆盖导入
            case COVER: cover.accept(upload);break;
//            差异导入
            case DIFFERENCE: difference.accept(upload);break;
        }
    }

    /**
     * 导出数据
     *
     * @param typeEnum 导出类型
     * @param request 导出请求
     * @param full 全量导出
     * @param select 导出勾中
     * @param query 导出查询结果
     * @return java.util.List<V>
     * @author shizejin
     * @date 2023/5/25 19:51
     */
    public static <R, V> List<V> exportData(ExportType typeEnum, R request ,
                                            Function<R, List<V>> full, Function<R, List<V>> select,
                                            Function<R, List<V>> query) {
        exportTypeThrowValid(typeEnum);

        List<V> dataList = Lists.newArrayList();
        switch (typeEnum) {
//            全量导出
            case FULL: dataList = full.apply(request);break;
//            导出勾中
            case SELECT: dataList = select.apply(request);break;
//            导出查询结果
            case QUERY: dataList = query.apply(request);break;
        }
        return dataList;
    }


    /**
     * 导出数据
     *
     * @param typeEnum 导出类型
     * @param request 导出请求
     * @param full 全量导出
     * @param select 导出勾中
     * @param query 导出查询结果
     * @return java.util.List<V>
     * @author shizejin
     * @date 2023/5/25 19:51
     */
    public static <R, V> List<V> exportData(Integer typeEnum, R request ,
                                            Function<R, List<V>> full, Function<R, List<V>> select,
                                            Function<R, List<V>> query) {
        return exportData(ExportType.getByCode(typeEnum), request, full, select, query);
    }

    /**
     * 上传类型校验
     *
     * @param typeEnum 上传类型
     * @author shizejin
     * @date 2023/5/25 17:34
     */
    private static void uploadTypeThrowValid(UploadType typeEnum) {
        if (typeEnum == null) {
            throw new BaseException("不支持的导入类型");
        }
    }

    private static void exportTypeThrowValid(ExportType typeEnum) {
        if (typeEnum == null) {
            throw new BaseException("不支持的导出类型");
        }
    }

    /**
     * 空数据校验
     *
     * @param dataList 数据
     * @author shizejin
     * @date 2023/5/26 11:36
     */
    private static void dataEmptyThrowValid(List dataList) {
        if (CollUtil.isEmpty(dataList)) {
            throw new BaseException("无数据");
        }
    }

    /**
     * 导出Excel
     *
     * @param response 响应
     * @param dataList 导出数据
     * @param cls 表头格式
     * @param fileName 文件名
     * @author shizejin
     * @date 2023/5/26 10:30
     */
    public static <V> void exportExcel(HttpServletResponse response, List<V> dataList, Class<V> cls,
                                       String fileName) {

        try {
            dataEmptyThrowValid(dataList);
            fileName = StrUtil.emptyToDefault(fileName, DateUtil.today());
            ExportUtil.exportHttpResponseHeader(response, fileName, FileTypeEnum.EXCEL);
            EasyExcel.write(response.getOutputStream(), cls).sheet().doWrite(dataList);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }



    /**
     * 导出Json
     *
     * @param response 响应
     * @param data 导出数据
     * @param fileName 文件名
     * @author shizejin
     * @date 2023/5/26 10:30
     */
    public static void exportJson(HttpServletResponse response, Object data, String fileName) {

        try {
            Assert.notNull(data, "导出数据不能为空");
            String jsonString = JSON.toJSONString(data);
            fileName = StrUtil.emptyToDefault(fileName, DateUtil.today());
            ExportUtil.exportHttpResponseHeader(response, fileName, FileTypeEnum.JSON);
            ServletOutputStream outputStream = response.getOutputStream();
//            将json写入流中
            IoUtil.write(outputStream, StandardCharsets.UTF_8, true, jsonString);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * 导出Json
     *
     * @param filePath 文件路径 /temp/a.txt
     * @param dataList 导出数据
     * @author shizejin
     * @date 2023/5/26 10:30
     */
    public static <V> void exportJson(String filePath, List<V> dataList) {
        dataEmptyThrowValid(dataList);
        FileUtil.touch(filePath);
        FileWriter fileWriter = new FileWriter(filePath, StandardCharsets.UTF_8);
        fileWriter.write(JSON.toJSONString(dataList));
    }

    /**
     * 导出zip文件
     *
     * @param response 响应流
     * @param file 要压缩的文件
     * @param zipName 压缩的文件名 ep: 上线物料
     * @author shizejin
     * @date 2023/5/26 10:30
     */
    public static void exportZip(HttpServletResponse response, File file, String zipName) {
        ExportUtil.exportHttpResponseHeader(response, "application/zip", zipName, FileTypeEnum.ZIP);
        try (ServletOutputStream outputStream = response.getOutputStream()) {
            ZipUtil.zip(outputStream, StandardCharsets.UTF_8, false, null, file);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
